#pragma once
#include "../net.hpp"
#include "../dispatcher.hpp"
#include "requestor.hpp"
#include <atomic>
#define MAX_DISCOVER_NUM 10 // 最大探测次数

// 服务发现客户端 : 连接注册中心

class DiscoveryClient
{
public:
    using ptr = std::shared_ptr<DiscoveryClient>;
    ~DiscoveryClient() {}
    DiscoveryClient(const Address &registerServer, const std::string &name, Requestor::ptr requestor, Dispatcher::ptr dispatcher)
        : _requestor(requestor), _dispatcher(dispatcher), _netClient(new MuduoClient(registerServer.first, registerServer.second, name))
    {
        _netClient->setHandlerProtocol(ProtocolFactory::create<LvProtocol>());

        // 发送请求
        _netClient->setMessageHander(std::bind(&Dispatcher::onMessage, _dispatcher.get(), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
        _dispatcher->setCallBack<ServiceResponse>(MSG_Type::SERVICE_RES, std::bind(&Requestor::onResponse, _requestor.get(), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
        _dispatcher->setCallBack<RpcResponse>(MSG_Type::RPC_RES, std::bind(&Requestor::onResponse, _requestor.get(), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
        _netClient->setCloseCb(std::bind(&DiscoveryClient::closed, this, std::placeholders::_1));
        _netClient->setConnCb(std::bind(&DiscoveryClient::onConnection, this, std::placeholders::_1));

        // 接收通知
        _dispatcher->setCallBack<ServiceNotify>(MSG_Type::SERVICE_NOTIFY, std::bind(&DiscoveryClient::getNotify, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
    }

    // 对外提发送请求的连接
    // 不直接返回：std::pair<std::string, BaseNet::ptr> 因为外面要判断是否创建连接对象，存在线程安全问题
    bool isCooectedRegister()
    {
        return _isConnectedRegister;
    }

    BaseNet::ptr getOneConn(const std::string &methodName)
    {
        int count = MAX_DISCOVER_NUM;
        while (true)
        {
            // 如果本地管理中已经存在
            {
                std::lock_guard<std::mutex> lock(_mutex);
                auto methodIt = _methodsConn.find(methodName); // map
                if (methodIt != _methodsConn.end())
                {
                    // 直接获取
                    auto it = _addressIndex.find(methodName);
                    if (it == _addressIndex.end())
                    {
                        LOG_ERROR << "错误";
                        return BaseNet::ptr(nullptr);
                    }

                    auto &index = it->second;
                    auto &toConn = (methodIt->second)[index++]; // pair
                    index %= methodIt->second.size();
                    Address addr = toConn.first;

                    if (toConn.second == nullptr)
                    {
                        std::string connName = methodName + "[" + AddressToString(addr) + "]";
                        toConn.second = NetFactory::creater<MuduoClient>(addr.first, addr.second, connName);
                        // 为连接客户端设置回调
                        toConn.second->setHandlerProtocol(ProtocolFactory::create<LvProtocol>());
                        toConn.second->setMessageHander(std::bind(&Dispatcher::onMessage, _dispatcher.get(), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
                        toConn.second->start();
                    }

                    // 设置一个超时连接: 10
                    int timeOut = 10;
                    while (timeOut > 0)
                    {
                        BaseConnection::ptr conn = toConn.second->getClientConnection();
                        if (conn->connected())
                        {
                            LOG_INFO << "连接成功：" << conn->perrAddressString();
                            return toConn.second;
                        }
                        LOG_INFO << "正在尝试连接..." << conn->perrAddressString();
                        --timeOut;
                        sleep(1);
                    }
                    if (timeOut == 0)
                    {
                        // 这里简略处理，认为连接暂时有问题
                        // 实际处理异常情况要更加复杂
                        ServiceNotify::ptr handle = MessageFactory::create<ServiceNotify>();
                        handle->setMethod(methodName);
                        handle->setHost(addr);
                        offline(handle);
                        LOG_WARN << "主机连接异常, 做下线处理：" << methodName << " " << AddressToString(addr);
                        return BaseNet::ptr(nullptr);
                    }
                }
            }
            // 本地管理不存在, 进行服务发现
            if (count > 0)
            {
                toRegisterDiscovery(methodName);
                count--;
            }
            else
            {
                return BaseNet::ptr(nullptr);
            }

        }
    }

    void connect()
    {
        _netClient->start();
    }

private:
    // 服务发现请求
    void toRegisterDiscovery(const std::string &methodName)
    {
        // 本地管理不存在, 进行服务发现
        ServiceRequest::ptr req = MessageFactory::create<ServiceRequest>();
        req->setType(MSG_Type::SERVICE_REQ);
        req->setId();
        req->setOptype(SERVICE_OPTYPE::DISCOVERY);
        req->setMethod(methodName);

        while (!_isConnectedRegister)
        {
            LOG_DEBUG << "正在连接服务注册中心...";
            sleep(1);
        }

        // 同步请求发送
        auto ret = _requestor->sendSync(_netClient->getClientConnection(), req, _netClient->getHandlerProtocol());
        if (ret.first)
        {
            // 发送成功
            ServiceResponse::ptr res = std::dynamic_pointer_cast<ServiceResponse>(ret.second);
            RCode code = res->getRcode();
            if (code != RCode::OK)
            {
                LOG_ERROR << errStr[code];
                return;
            }

            {
                std::lock_guard<std::mutex> lock(_mutex);
                std::vector<Address> addrVt = res->getHosts();
                // 代码复用
                for (auto &add : addrVt)
                {
                    ServiceNotify::ptr msg = MessageFactory::create<ServiceNotify>();
                    msg->setMethod(methodName);
                    msg->setHost(add);
                    online(msg);
                }
            }
            return;
        }

        LOG_ERROR << "服务发现请求发送失败";
    }

    // 主动收到服务注册中心的通知
    void getNotify(BaseConnection::ptr conn, ServiceNotify::ptr notifyMsg, BaseProtocol::ptr protocol)
    {
        SERVICE_OPTYPE opType = notifyMsg->getOptype();

        if (opType == SERVICE_OPTYPE::ONLINE)
        {
            // 上线通知
            std::lock_guard<std::mutex> lock(_mutex);
            online(notifyMsg);
        }
        else if (opType == SERVICE_OPTYPE::OFFLINE)
        {
            // 下线通知
            std::lock_guard<std::mutex> lock(_mutex);
            offline(notifyMsg);
        }
        else
        {
            LOG_ERROR << "未知错误";
        }
    }

    void online(ServiceNotify::ptr notifyMsg)
    {
        std::string methodName = notifyMsg->getMethod();
        Address addr = notifyMsg->getHost();
        std::string addrStr = AddressToString(addr);

        // 上线通知
        auto methodIt = _methodsConn.find(methodName);
        if (methodIt == _methodsConn.end())
        {
            // 此服务是第一次上线
            _methodsConn.insert({methodName, std::vector<std::pair<Address, BaseNet::ptr>>{{addr, nullptr}}});
            _addressIndex.insert({methodName, 0});
            LOG_INFO << "上线服务：" << methodName << " " << addrStr;
            return;
        }

        // 是否是不同主机提供的相同服务
        for (auto &e : methodIt->second)
        {
            if (e.first == addr)
            {
                // 重复的主机
                LOG_INFO << "此上线服务的主机已经存在：" << methodName << " " << addrStr;
                return;
            }
        }

        // 没有重复
        methodIt->second.emplace_back(addr, nullptr);
        LOG_INFO << "为 " << methodName << "服务上线新的主机：" << addrStr;
    }

    void offline(ServiceNotify::ptr notifyMsg)
    {
        std::string methodName = notifyMsg->getMethod();
        Address addr = notifyMsg->getHost();
        std::string addrStr = AddressToString(addr);

        // 下线通知
        auto methodIt = _methodsConn.find(methodName);
        if (methodIt == _methodsConn.end())
        {
            LOG_WARN << "异常下线通知，没有发现过这个服务：" << methodName << " 追踪消息ID = " << notifyMsg->getId();
            return;
        }

        // 存在管理结构中
        auto connIt = methodIt->second.begin();
        while (connIt != methodIt->second.end())
        {
            if (connIt->first == addr)
            {
                // 找到
                break;
            }
            ++connIt;
        }

        if (connIt == methodIt->second.end())
        {
            LOG_WARN << "异常下线通知，这个服务对应的主机地址没有在本地管理中：" << methodName << " " << addrStr << " 追踪消息ID = " << notifyMsg->getId();
            return;
        }

        // 进行下线删除
        methodIt->second.erase(connIt);
        if (methodIt->second.size() != 0 && _addressIndex.find(methodName) != _addressIndex.end())
        {
            _addressIndex.find(methodName)->second %= methodIt->second.size();
        }
        LOG_INFO << "服务下线成功：" << methodName << " " << addrStr;

        if (methodIt->second.empty())
        {
            _methodsConn.erase(methodIt);
            _addressIndex.erase(methodName);
            LOG_INFO << "可以提供这个服务的主机全部下线：" << methodName;
        }
    }

    std::atomic<bool> _isConnectedRegister{false};
    void onConnection(BaseConnection::ptr conn)
    {
        _isConnectedRegister = true;
        LOG_DEBUG << "连接到注册服务中心：" << conn->perrAddressString();
    }

    void closed(BaseConnection::ptr conn)
    {
        _isConnectedRegister = false;
        LOG_DEBUG << "和注册服务中心的连接断开：" << conn->perrAddressString();
    }

private:
    Requestor::ptr _requestor;   // 请求 和 响应 一 一 对应管理
    Dispatcher::ptr _dispatcher; //  消息分类调度
    BaseNet::ptr _netClient;

    std::mutex _mutex;
    std::unordered_map<std::string, std::vector<std::pair<Address, BaseNet::ptr>>> _methodsConn; // 采取长连接的方式，管理已经发现的服务: 连接池
    std::unordered_map<std::string, int> _addressIndex;                                          // 使用轮询的方式去获取一个连接实现简单负载
};

typedef DiscoveryClient::ptr ConnectionPool; // 客户端不直接连接服务端
